import React, {Component} from 'react'
import {MarkdownPreview} from 'react-marked-markdown';
// import py1_1 from '../static/py1_1.md'
// import marked from 'marked';

class Py24 extends Component {
    constructor(props) {
        super(props);
        this.state = {
           value: `

客户端脚本语言是运行在浏览器而非服务器上的语言。客户端语言成功的前提是浏览器拥
有正确地解释和执行这类语言的能力（这也是在浏览器上禁止 JavaScript 非常容易的原因）。

在一定程度上，由于很难让所有浏览器开发商都认可同一个标准，所以客户端语言比服务
器端语言要少很多。不过这在网络数据采集的时候是件好事：要处理的语言越少越好。

通常，你在网上遇到的客户端语言只有两种：ActionScript（开发 Flash 应用的语言）和
JavaScript。今天 ActionScript 的使用率比 10 年前低很多，经常用于流媒体文件播放，用作
在线游戏平台，或者是网站上那些没人想看更没人点击的“介绍”页面。总之，采集 Flash
页面的需要并不多，所以我重点介绍新式网页中普遍使用的客户端语言：JavaScript。

到目前为止，JavaScript 是网络上最常用也是支持者最多的客户端脚本语言。它可以收集
用户的跟踪数据，不需要重载页面直接提交表单，在页面嵌入多媒体文件，甚至运行网页
游戏。那些看起来非常简单的页面背后通常使用了许多 JavaScript 文件。你可以在网页源
代码的 \<script> 标签之间看到它们：
  
    <script>
    alert("This creates a pop-up using JavaScript");
    </script>
 

> 10.1　JavaScript简介

对要采集的语言预先做些了解会很有用。自己熟悉一下 JavaScript 总会有好处。

JavaScript 是一种弱类型语言，其语法通常可以与 C++ 和 Java 做对比。虽然语法中的一些
元素，比如操作符、循环条件和数组，都与 C++、Java 语法很接近，但是 JavaScript 的弱
类型和脚本形式被一些程序员看成是折磨人的怪兽。

例如，下面的 JavaScript 程序通过递归方式计算 Fibonacci 序列，最后把结果打印在浏览器
的开发者控制台里：
   
    <script>
    function fibonacci(a, b){
        var nextNum = a + b;
        console.log(nextNum+" is in the Fibonacci sequence");
        if(nextNum < 100){
            fibonacci(b, nextNum);
        }
    }
    fibonacci(1, 1);
    </script>
  
注意 JavaScript 里所有的变量都用 var 关键词字进行定义。这与 PHP 里的 $ 符号类似，或
者 Java 和 C++ 里的类型声明（ int 、 String 、 List 等）。Python 不太一样，它没有这种显
式的变量声明。

JavaScript 还有一个非常好的特性，就是把函数作为变量使用：
    
    <script>
    var fibonacci = function() {
        var a = 1;
        var b = 1;
        return function() {
            var temp = b;
            b = a + b;
            a = temp;
            return b;
        }
    }
    var fibInstance = fibonacci();
    console.log(fibInstance()+" is in the Fibonacci sequence");
    console.log(fibInstance()+" is in the Fibonacci sequence");
    console.log(fibInstance()+" is in the Fibonacci sequence");
    </script>
  
第一次看到这段代码可能有点儿头晕，不过如果你把这种特性看成 Lambda 表达式（第 2
章介绍过），就会很简单了。变量 fibonacci 被定义成一个函数。它的函数值返回一个递增
的 Fibonacci 序列里较大的值。每次当它被调用时会返回 Fibonacci 的计算函数，再次执行
序列计算，并增加函数变量的值。

虽然这样看起来有点儿复杂，但是在解决一些问题时，比如计算 Fibonacci 序列值，这种
模式还是比较合适的。在处理用户行为和回调函数时，把函数作为变量进行传递是非常方
便的，另外在阅读 JavaScript 代码的时候必须适应这种编程方式。

#### 常用JavaScript库

虽然了解 JavaScript 语言本身的语法很重要，但是在新式的网络中你必然要使用至少一
种 JavaScript 语言的第三方库。在查看网页源代码的时候，你可能会看到很多常用的
JavaScript 库。

用 Python 执行 JavaScript 代码的效率非常低，既费时又费力，尤其是在处理规模较大的
JavaScript 代码时。如果有绕过 JavaScript 并直接解析它的方法（不需要执行它就可以获得
信息）会非常实用，可以帮你避开一大堆 JavaScript 的麻烦事。

1. jQuery

jQuery 是一个十分常见的库，70% 最流行的网站（约 200 万）和约 30% 的其他网站（约 2
亿）都在使用。 1 一个网站使用 jQuery 的特征，就是源代码里包含了 jQuery 入口，比如：
    
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
     
如果你在一个网站上看到了 jQuery，那么采集这个网站数据的时候要格外小心。jQuery 可
以动态地创建 HTML 内容，只有在 JavaScript 代码执行之后才会显示。如果你用传统的方
法采集页面内容，就只能获得 JavaScript 代码执行之前页面上的内容。

另外，这些页面还可能包含动画、用户交互内容和嵌入式媒体，这些内容对网络数据采集
都是挑战。

2. Google Analytics

有一半的网站都在用 Google Analytics 2 ，它可能是网站最常用的 JavaScript 库和最受欢迎的
用户跟踪工具。其实，http://pythonscraping.com 和 http://www.oreilly.com/ 都用了 Google
Analytics。

很容易判断一个页面是不是使用了 Google Analytics。如果网站使用了它，在页面底部会有
类似如下所示的 JavaScript 代码（取自 O’Reilly Media 网站）:
   
    <!-- Google Analytics -->
    <script type="text/javascript">
    var _gaq = _gaq || [];
    _gaq.push(['_setAccount', 'UA-4591498-1']);
    _gaq.push(['_setDomainName', 'oreilly.com']);
    _gaq.push(['_addIgnoredRef', 'oreilly.com']);
    _gaq.push(['_setSiteSpeedSampleRate', 50]);
    _gaq.push(['_trackPageview']);

    (function() { var ga = document.createElement('script'); ga.type =
    'text/javascript'; ga.async = true; ga.src = ('https:' ==
    document.location.protocol ? 'https://ssl' : 'http://www') +
    '.google-analytics.com/ga.js'; var s =
    document.getElementsByTagName('script')[0];
    s.parentNode.insertBefore(ga, s); })();

    </script>
 
如果一个网站使用了 Google Analytics 或其他类似的网络分析系统，而你不想让网站知道
你在采集数据，就要确保把那些分析工具的 cookie 或者所有 cookie 都关掉。

3. Google地图

只要你上过网，就一定见过内嵌 Google 地图的网站。用 Google 地图的 API 很容易在任何
网站上嵌入地图。

如果你要采集任何的位置数据，理解 Google 地图的工作方式可以让你轻松地获取格式规
范的经纬度坐标和具体地址。在 Google 地图上，显示一个位置最常用的方法就是用标记
（一个大头针）。

标记可以用下面的代码插在 Google 地图上：
 
    var marker = new google.maps.Marker({
    position: new google.maps.LatLng(-25.363882,131.044922),
    map: map,
    title: 'Some marker text'
    });
     
Python 可以轻松地抽取出所有位置在 google.maps.LatLng() 里的坐标，生成一组经 / 纬度
坐标值。

通过 Google 的“地理坐标反向查询”API（https://developers.google.com/maps/documentation/javascript/examples/geocoding-reverse），你可以把这些经纬度坐标组解析成格式规范的地
址，便于存储和分析。

> 10.2　Ajax和动态HTML

到目前为止，我们与网站服务器通信的唯一方式，就是发出 HTTP 请求获取新页面。如果
提交表单之后，或从服务器获取信息之后，网站的页面不需要重新刷新，那么你访问的网
站就在用 Ajax 技术。

与一些人的印象不太一样，Ajax 其实并不是一门语言，而是用来完成网络任务（可以认为
它与网络数据采集差不多）的一系列技术。Ajax 全称是 Asynchronous JavaScript and XML
（异步 JavaScript 和 XML），网站不需要使用单独的页面请求就可以和网络服务器进行交互
（收发信息）。需要注意的是：你不应该说“这个网站是 Ajax 写的”。正确的说法应该是
“这个表单用 Ajax 与网络服务器通信”。

和 Ajax 一样，动态 HTML（dynamic HTML，DHTML）也是一系列用于解决网络问题的
技术集合。DHTML 是用客户端语言改变页面的 HTML 元素（HTML、CSS，或者二者皆
被改变）。比如，页面上的按钮只有当用户移动鼠标之后才出现，背景色可能每次点击都
会改变，或者用一个 Ajax 请求触发页面加载一段新内容。

值得注意的是，虽然“动态”这个词往往和“移动”或“变化”联系在一起，但是那些使
用了交互 HTML 组件、图像可以移动，或者带有嵌入式媒体文件的网页，并不一定就是动
态 HTML，即使页面看起来是动态的。另外，一些表面看起来极其单调、静态的页面，底
层却可能是用 DHTML 处理的，关键要看有没有用 JavaScript 控制 HTML 和 CSS 元素。

如果你采集过许多网站，很可能会遇到这样一种情况。你在浏览器上看到的内容，与你用
爬虫从网站上采集的内容不一样。你可能会怀疑自己是不是哪个细节没处理好，希望找出
内容采集不出来的原因。

有时你还会发现，网页用一个加载页面把你引到另一个页面上，但是网页的 URL 链接在
这个过程中一直没有变化。

这些都是因为你的爬虫不能执行那些让页面产生各种神奇效果的 JavaScript 代码。如果网
站的 HTML 页面没有运行 JavaScript，就可能和你在浏览器里看到的样子完全不同，因为
浏览器可以正确地执行 JavaScript。

那些使用了 Ajax 或 DHTML 技术改变 / 加载内容的页面，可能有一些采集手段，但是用
Python 解决这个问题只有两种途径：直接从 JavaScript 代码里采集内容，或者用 Python 的
第三方库运行 JavaScript，直接采集你在浏览器里看到的页面。

#### 在Python中用Selenium执行JavaScript

Selenium（http://www.seleniumhq.org/）是一个强大的网络数据采集工具，其最初是为网
站自动化测试而开发的。近几年，它还被广泛用于获取精确的网站快照，因为它们可以直
接运行在浏览器上。Selenium 可以让浏览器自动加载页面，获取需要的数据，甚至页面截
屏，或者判断网站上某些动作是否发生。

Selenium 自己不带浏览器，它需要与第三方浏览器结合在一起使用。例如，如果你在
Firefox 上运行 Selenium，可以直接看到一个 Firefox 窗口被打开，进入网站，然后执行你
在代码中设置的动作。虽然这样可以看得更清楚，但是我更喜欢让程序在后台运行，所以
我用一个叫 PhantomJS（http://phantomjs.org/download.html）的工具代替真实的浏览器。

PhantomJS 是一个“无头”（headless）浏览器。它会把网站加载到内存并执行页面上的
JavaScript，但是它不会向用户展示网页的图形界面。把 Selenium 和 PhantomJS 结合在一
起，就可以运行一个非常强大的网络爬虫了，可以处理 cookie、JavaScrip、header，以及
任何你需要做的事情。

你可以从 PyPI 网站（https://pypi.python.org/simple/selenium/）下载 Selenium 库，也可以用
第三方管理器（像 pip）用命令行安装。

PhantomJS 也 可 以 从 它 的 官 方 网 站（http://phantomjs.org/download.html） 下 载。 因 为
PhantomJS 是一个功能完善（虽然无头）的浏览器，并非一个 Python 库，所以它不需要像
Python 的其他库一样安装，也不能用 pip 安装。

虽然有很多页面都用 Ajax 加载数据（尤其是 Google），我还是在 http://pythonscraping.com/pages/javascript/ajaxDemo.html 建了一个简单的页面来运行我们的爬虫。这个页面上有一些
简单的文字，是手工敲在 HTML 代码里的，打开页面两秒钟之后，页面就会被替换成一个
Ajax 生成的内容。如果我们用传统的方法采集这个页面，只能获取加载前的页面，而我们
真正需要的信息（Ajax 执行之后的页面）却抓不到。

Selenium 库是一个在 WebDriver 上调用的 API。WebDriver 有点儿像可以加载网站的浏览
器，但是它也可以像 BeautifulSoup 对象一样用来查找页面元素，与页面上的元素进行交互
（发送文本、点击等），以及执行其他动作来运行网络爬虫。

下面的代码可以获取前面测试页面上 Ajax“墙”后面的内容：
    
    from selenium import webdriver
    import time

    driver = webdriver.PhantomJS(executable_path='')
    driver.get("http://pythonscraping.com/pages/javascript/ajaxDemo.html")
    time.sleep(3)
    print(driver.find_element_by_id('content').text)
    driver.close()
    
这段代码用 PhantomJS 库创建了一个新的 Selenium WebDriver，首先用 WebDriver 加载页
面，然后暂停执行 3 秒钟，再查看页面获取（希望已经加载完成的）内容。

依据你的 PhantomJS 安装位置，在创建新的 PhantomJS WebDriver 的时候，你需要在
Selenium 的 WebDriver 接入点指明 PhantomJS 可执行文件的路径：
    
    driver = webdriver.PhantomJS(executable_path='/path/to/download/phantomjs-1.9.8-macosx/bin/phantomjs')
     


<br/>
<center>Selenium 的选择器</center>

 
在之前的几章里，我们用过 BeautifulSoup 的选择器选择页面的元素，比如 find 和findAll 。Selenium 在 WebDriver 的 DOM 中使用了全新的选择器来查找元素，不过它们都使用非常直截了当的名称。

在这个例子中，我们用的选择器是 find_element_by_id ，虽然下面的选择器也可以获取同样的结果：

    driver.find_element_by_css_selector("#content")
    driver.find_element_by_tag_name("div")

当然，如果你想选择页面上具有同样特征的多个元素，可以用 elements （换成复数）来返回一个 Python 列表

    driver.find_elements_by_css_selector("#content")
    driver.find_elements_by_css_selector("div")

另外，如果你还是想用 BeautifulSoup 来解析网页内容，可以用 WebDriver 的 page_source 函数返回页面的源代码字符串。

    pageSource = driver.page_source
    bsObj = BeautifulSoup(pageSource)
    print(bsObj.find(id="content").get_text())

如果程序配置都正确，上面的程序会在几秒钟以后显示下面的结果：
   
    Here is some important text you want to retrieve!
    A button to click!
   
需要注意的是，虽然页面里有一个元素是 HTML 按钮，但是 Selenium 的 .text 函数可以
获取按钮的文本内容，就像它获取页面上其他元素内容的方式一样。

如果 time.sleep 的暂停时间由三秒改成一秒，那么上面程序采集的文本就会变成：
    
    This is some content that will appear on the page while it's loading.
    You don't care about scraping this.
     
虽然这个方法奏效了，但是效率还不够高，在处理规模较大的网站时还是可能会出问题。
页面的加载时间是不确定的，具体依赖于服务器某一毫秒的负载情况，以及不断变化的网
速。虽然这个页面加载可能只需要花两秒多的时间，但是我们设置了三秒的等待时间以确
保页面完全加载成功。一种更加有效的方法是，让 Selenium 不断地检查某个元素是否存
在，以此确定页面是否已经完全加载，如果页面加载成功就执行后面的程序。

下面的程序用 id 是 loadedButton 的按钮检查页面是不是已经完全加载：
     
    from selenium.webdriver.common.by import By
    from selenium.webdriver.support.ui import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC

    driver = webdriver.PhantomJS(executable_path='')
    driver.get("http://pythonscraping.com/pages/javascript/ajaxDemo.html")
    try:
        element = WebDriverWait(driver, 10).until(
                            EC.presence_of_element_located((By.ID, "loadedButton")))
    finally:
        print(driver.find_element_by_id("content").text)
        driver.close()
    
程序里新导入了一些新的模块，最需要注意的就是 WebDriverWait 和 expected_conditions ，
这两个模块组合起来构成了 Selenium 的隐式等待（implicit wait）。

隐式等待与显式等待的不同之处在于，隐式等待是等 DOM 中某个状态发生后再继续运行
代码（没有明确的等待时间，但是有最大等待时限，只要在时限内就可以），而显式等待
明确设置了等待时间，如前面例子的等待三秒钟。在隐式等待中，DOM 触发的状态是用
expected_conditions 定义的（这里导入后用了别名 EC ，是经常使用的简称）。在 Selenium
库里面元素被触发的期望条件（expected condition）有很多种，包括：

* 弹出一个提示框
* 一个元素被选中（比如文本框）
* 页面的标题改变了，或者某个文字显示在页面上或者某个元素里
* 一个元素在 DOM 中变成可见的，或者一个元素从 DOM 中消失了

当然，大多数的期望条件在使用前都需要你先指定等待的目标元素。元素用定位器
（locator）指定。注意，定位器与选择器是不一样的（请看前面关于选择器的介绍）。定位
器是一种抽象的查询语言，用 By 对象表示，可以用于不同的场合，包括创建选择器。
在下面的示例代码中，一个定位器被用来查找 id 是 loadedButton 的按钮：
    
    EC.presence_of_element_located((By.ID, "loadedButton"))
    
定位器还可以用来创建选择器，配合 WebDriver 的 find_element 函数使用：
   
    print(driver.find_element(By.ID, "content").text)
   
下面这行代码的功能和示例代码中一样：
    
    print(driver.find_element_by_id("content").text)
     
如果你可以不用定位器，就不要用，毕竟这样可以少导入一个模块。但是，定位器是一种
十分方便的工具，可以用在不同的应用中，并且具有很好的灵活性。

下面是定位器通过 By 对象进行选择的策略。

*  ID

在上面的例子里用过；通过 HTML 的 id 属性查找元素。

*  CLASS_NAME

通过 HTML 的 class 属性来查找元素。为什么这个函数是 CLASS_NAME ，而不是简单的
CLASS ？在 Selenium 的 Java 库里使用 object.CLASS 可能会出现问题， .class 是 Java 保
留的一个方法。为了让 Selenium 语法可以兼容不同的语言，就用 CLASS_NAME 代替。

*  CSS_SELECTOR

通过 CSS 的 class 、 id 、 tag 属性名来查找元素，用 #idName 、 .className 、 tagName
表示。

*  LINK_TEXT

通过链接文字查找 HTML 的 \<a> 标签。例如，如果一个链接的文字是“Next”，就可以
用 (By.LINK_TEXT, "Next") 来选择。

*  PARTIAL_LINK_TEXT

与 LINK_TEXT 类似，只是通过部分链接文字来查找。

*  NAME

通过 HTML 标签的 name 属性查找。这在处理 HTML 表单时非常方便。

*  TAG_NAME

通过 HTML 标签的名称查找。

*  XPATH

用 XPath 表达式（语法在下面介绍）选择匹配的元素。

<center>XPath 语法</center>
 
    XPath（XML Path，XML 路径）是在 XML 文档中导航和选择元素的查询语言。它由
    W3C 于 1999 年创建，在 Python、Java 和 C# 这些语言中有时会用 XPath 来处理 XML
    文档。

    虽然 BeautifulSoup 不支持 XPath，但是本书中的很多库（lxml、Selenium、Scrapy 等）
    都支持。它的使用方式通常和 CSS 选择器（比如 mytag#idname ）一样，虽然它原本被
    设计用于处理更规范的 XML 文档而不是 HTML 文档。

    在 XPath 语法中有四个重要概念。

    • 根节点和非根节点
        ♦ /div 选择 div 节点，只有当它是文档的根节点时
        ♦ //div 选择文档中所有的 div 节点（包括非根节点）
    • 通过属性选择节点
        ♦ //@href 选择带 href 属性的所有节点
        ♦ //a[@href='http://google.com'] 选择页面中所有指向 Google 网站的链接
    • 通过位置选择节点
        ♦ //a[3] 选择文档中的第三个链接
        ♦ //table[last()] 选择文档中的最后一个表
        ♦ //a[position() < 3] 选择文档中的前三个链接
    • 星号（ * ）匹配任意字符或节点，可以在不同条件下使用
        ♦ //table/tr/* 选择所有表格行 tr 标签的所有的子节点（这很适合选择 th 和 td 标
        签）
        ♦ //div[@*] 选择带任意属性的所有 div 标签

    当然，XPath 还有很多高级的语法特征。经过这些年的发展，它已经变成一种非常复
    杂的查询语言，可以使用布尔类型、函数（如 position() ），以及大量这里没介绍的操
    作符。

    如果这里介绍的几个 XPath 功能解决不了你的 HTML 或 XML 元素选择问题，请参考
    微软的 XPath 语法页面（https://msdn.microsoft.com/en-us/enus/library/ms256471）。
 

> 10.3　处理重定向

客户端重定向是在服务器将页面内容发送到浏览器之前，由浏览器执行 JavaScript 完成的
页面跳转，而不是服务器完成的跳转。当使用浏览器访问页面的时候，有时很难区分这两
种重定向。由于客户端重定向执行很快，加载页面时你甚至感觉不到任何延迟，所以会让
你觉得这个重定向就是一个服务器端重定向。

但是，在进行网络数据采集的时候，这两种重定向的差异是非常明显的。根据具体情况，
服务器端重定向一般都可以轻松地通过 Python 的 urllib 库解决，不需要使用 Selenium
（更多的介绍请参考第 3 章）。客户端重定向却不能这样处理，除非你有工具可以执行
JavaScript。

Selenium 可以执行这种 JavaScript 重定向，和它处理其他 JavaScript 的方式一样；但是这类
重定向的主要问题是什么时候停止页面监控，也就是说，怎么识别一个页面已经完成重定
向。在 http://pythonscraping.com/pages/javascript/redirectDemo2.html 的示例页面是客户端重
定向的例子，有两秒的延迟。

我们可以用一种智能的方法来检测客户端重定向是否完成，首先从页面开始加载
时就“监视”DOM 中的一个元素，然后重复调用这个元素直到 Selenium 抛出一个
StaleElementReferenceException 异常；也就是说，元素不在页面的 DOM 里了，说明这时
网站已经跳转：


    from selenium import webdriver
    import time
    from selenium.webdriver.remote.webelement import WebElement
    from selenium.common.exceptions import StaleElementReferenceException

    def waitForLoad(driver):
        elem = driver.find_element_by_tag_name("html")
        count = 0
        while True:
            count += 1
            if count > 20:
                print("Timing out after 10 seconds and returning")
                return
            time.sleep(.5)
            try:
                elem == driver.find_element_by_tag_name("html")
            except StaleElementReferenceException:
                return
    driver = webdriver.PhantomJS(executable_path='<Path to Phantom Js>')
    driver.get("http://pythonscraping.com/pages/javascript/redirectDemo1.html")
    waitForLoad(driver)
    print(driver.page_source)
  
这个程序每半分钟检查一次网页，看看 html 标签还在不在，时限为 10 秒钟，不过检查的
时间间隔和时限都可以根据实际情况随意调整。
    
           
           `,
        };
      }
    render() {
        return (
            <div >
                {/* 111111111111111
                <Py112 /> */}
                <MarkdownPreview value={this.state.value}/>
            </div>
        )
    }
}

export default Py24